package edu.gatech.fiveminutesleft.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import android.util.Log;
import edu.gatech.fiveminutesleft.cache.AccountCache;
import edu.gatech.fiveminutesleft.storage.StorageAddress;

/**
 * A controller that encapsulates the retrieval and interactions with the task lists that belong to
 * a certain account. Also a facade for the actual object data of the tasklists stored in the cache.
 * 
 * @author Andrey Kurenkov
 */
public class AppAccountLists implements AccountLists {

	private AccountCache					myCache;

	private final String					LIST_COUNT_FIELD			= "TaskListCount";
	// Prefix for key to store a TaskList's name - needs to be completed with the list's number(one
	// higher than current
	// count)
	private final String					LIST_NAME_FIELD				= "Name of List #"/*
																						 * +<List
																						 * Index>
																						 */;
	// Prefix for key to store a TaskList's index- needs to be completed with name
	private final String					LIST_INDEX_FIELD			= "Index of List "/*
																						 * +<List
																						 * Name>
																						 */;
	// Prefix for key to store a TaskList's server address - needs to be completed with name
	private final String					LIST_SERVER_ADDRESS_FIELD	= "Server Address of List "/*
																									 * +
																									 * <
																									 * List
																									 * Name
																									 * >
																									 */;
	// Prefix for key to store a TaskList's object address - needs to be completed with name
	private final String					LIST_OBJECT_ADDRESS_FIELD	= "Object Address of List "/*
																									 * +
																									 * <
																									 * List
																									 * Name
																									 * >
																									 */;
	public static final String				ERROR_LIST_NAME_REPLICATED	= "Lists cannot have the same name";
	public static final String				ERROR_LIST_NAME_NOT_FOUND	= "List name not found";
	/*
	 * This Map is needed because of an issue in the cahce layer that caused it to incessantly
	 * retrieve objects from storage whenever a list was gotten.
	 * 
	 * Used in getTaskList
	 * 
	 * TODO:fix issue in Cache that makes this needed, since cache should take care of this
	 */
	private HashMap<String, AppTaskList>	retrieved;

	/**
	 * Constructor that get the cache in which taskList information is actually stored
	 * 
	 * @param accountCache
	 *            the cache from which to get taskList information
	 */
	public AppAccountLists(AccountCache accountCache) {
		myCache = accountCache;
		retrieved = new HashMap<String, AppTaskList>();
	}

	/**
	 * Adds the given taskList with the given name to this account
	 * 
	 * @param addList
	 *            the list to add
	 * @param name
	 *            the name of the list
	 * @return null on success, error otherwise
	 */
	public String addTaskList(TaskList addList, String name) {
		AppTaskList list = AppTaskList.clone(addList);
		if (Arrays.asList(this.getListNames()).contains(name)) {
			return ERROR_LIST_NAME_REPLICATED;
		}

		// if the task is unbound, bind it to our storage location
		if (list.getAddress() == null) {
			list.bind(this.myCache.getStorage().createTaskList());
			Log.d("AccountLists.addTaskList", "bound newly-created list to " + list.getAddress());
		}

		int currentTaskListCount = getNumLists();
		incrementNumLists();
		myCache.setField(LIST_NAME_FIELD + currentTaskListCount, name);
		myCache.setField(LIST_INDEX_FIELD + name, "" + currentTaskListCount);
		myCache.setField(LIST_SERVER_ADDRESS_FIELD + name, list.getAddress().getServerAddress());
		myCache.setField(LIST_OBJECT_ADDRESS_FIELD + name, list.getAddress().getObjectAddress());
		return null;

	}

	/**
	 * Adds a new taskList with the given name to this account
	 * 
	 * @param name
	 *            the name of the list
	 * @return null on success, error otherwise
	 */
	public String addTaskList(String name) {
		AppTaskList list = new AppTaskList();

		if (Arrays.asList(this.getListNames()).contains(name)) {
			return ERROR_LIST_NAME_REPLICATED;
		}
		list.setName(name);
		// if the task is unbound, bind it to our storage location
		if (list.getAddress() == null) {
			list.bind(this.myCache.getStorage().createTaskList());
			Log.d("AccountLists.addTaskList", "bound newly-created list to " + list.getAddress());
		}

		int currentTaskListCount = getNumLists();
		incrementNumLists();
		myCache.setField(LIST_NAME_FIELD + currentTaskListCount, name);
		myCache.setField(LIST_INDEX_FIELD + name, "" + currentTaskListCount);
		myCache.setField(LIST_SERVER_ADDRESS_FIELD + name, list.getAddress().getServerAddress());
		myCache.setField(LIST_OBJECT_ADDRESS_FIELD + name, list.getAddress().getObjectAddress());
		return null;

	}

	/**
	 * Returns a list with a specific name
	 * 
	 * @param name
	 *            the name of the list to get
	 * @return the TaskList with the specifed name
	 */
	public AppTaskList getTaskList(String name) {
		String serverAddress = myCache.getField(LIST_SERVER_ADDRESS_FIELD + name);
		String objectAddress = myCache.getField(LIST_OBJECT_ADDRESS_FIELD + name);
		if (serverAddress == null || objectAddress == null)
			return null;
		if (!retrieved.containsKey(name)) {
			AppTaskList toReturn = AppTaskList.locate(new StorageAddress(serverAddress, objectAddress));
			if (toReturn == null)
				return null;
			toReturn.setName(name);
			retrieved.put(name, toReturn);
			return toReturn;
		}
		return retrieved.get(name);
	}

	/**
	 * Retrieves from storage and returns all the TaskLists owned or associated with the account
	 * 
	 * @return an ArrayList containing all the TaskLists belonging to this account
	 */
	public ArrayList<TaskList> getTaskLists() {
		ArrayList<TaskList> lists = new ArrayList<TaskList>();
		int currentTaskListCount = getNumLists();
		for (int i = 0; i < currentTaskListCount; i++) {
			String name = myCache.getField(LIST_NAME_FIELD + i);
			TaskList l = getTaskList(name);
			if (l != null) {
				l.setName(name);
				lists.add(l);
			}
		}
		return lists;
	}

	/**
	 * Returns the names of all the tasklists that belong to this account
	 * 
	 * @return a string array with the lists' names
	 */
	public String[] getListNames() {
		int currentTaskListCount = getNumLists();
		String[] names = new String[currentTaskListCount];
		for (int i = 0; i < currentTaskListCount; i++) {
			names[i] = myCache.getField(LIST_NAME_FIELD + i);
		}
		return names;
	}

	/**
	 * Returns the number of lists currently in storage
	 * 
	 * @return 0 if no lists stored previously, number of lists otherwise
	 */
	public int getNumLists() {
		String listNumField = myCache.getField(LIST_COUNT_FIELD);
		if (listNumField == null) {
			myCache.setField(LIST_COUNT_FIELD, "0");
			return 0;
		}
		return Integer.parseInt(listNumField);
	}

	/**
	 * Decrements the field storing the amounts of lists
	 */
	private void incrementNumLists() {
		int currentTaskListCount = getNumLists();
		myCache.setField(LIST_COUNT_FIELD, "" + (currentTaskListCount + 1));
	}

	/**
	 * Increments the field storing the amounts of lists
	 */
	private void decrementNumLists() {
		int currentTaskListCount = getNumLists();
		if (currentTaskListCount > 0)
			myCache.setField(LIST_COUNT_FIELD, "" + (currentTaskListCount - 1));
	}

	/**
	 * Deletes tha TaskList with the given name
	 * 
	 * @param name
	 *            The name of the tasklist to delete
	 * @return the deleted TaskList
	 */
	public AppTaskList deleteTaskList(String name) {
		AppTaskList toRemove = getTaskList(name);
		if (toRemove != null) {
			int index = Integer.parseInt(myCache.getField(LIST_INDEX_FIELD + name));
			decrementNumLists();
			for (int i = index; i < getNumLists(); i++) {
				// Shift indexes of all following lists down
				myCache.setField(LIST_NAME_FIELD + i, myCache.getField(LIST_NAME_FIELD + (i + 1)));
				myCache.setField(LIST_INDEX_FIELD + myCache.getField(LIST_NAME_FIELD + i), "" + i);
			}
		}
		toRemove.delete();
		return toRemove;
	}

	/**
	 * Creates and returns an AppTaskList for use by code outside this package.
	 * 
	 * @return a new AppTaskList
	 */
	public TaskList makeTaskList() {
		return new AppTaskList();
	}

}
